﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace NetworkCrashSimulator.Core
{
    public enum ActionError : short
    {
        NoError = 0,
        UnableToCrash = 1,
        UnableToRepair = 2,
        UnableToList = 3,
        AdapterNotFound = 4,
        AdapterConfigurationNotSupported = 5,
        ConfigurationFileNotFound = 6,
        UnableToCreateConfigurationFile = 7,
        UnableToLoadConfigurationFile = 8
    }

    public class Actions
    {
        /// <summary>
        /// File name for the file that will hold the network adapter configuration.
        /// </summary>
        const string saveFileName = "AdapterConfig.ncs";

        /// <summary>
        /// Generate a console line following the application template.
        /// </summary>
        /// <param name="message">Message to show on the console line.</param>
        /// <remarks>Date and time will appear in the console line.</remarks>
        internal static void GenerateConsoleLine(string message)
        {
            GenerateConsoleLine(message, true);
        }

        /// <summary>
        /// Generate a console line following the application template.
        /// </summary>
        /// <param name="message">Message to show on the console line.</param>
        /// <param name="addDateTime">Date time should be added to the console line?</param>
        internal static void GenerateConsoleLine(string message, bool addDateTime)
        {
            if (addDateTime)
            {
                message = string.Format("[{0}] {1}", DateTime.Now.ToString(), message);
            }

            Console.WriteLine(message);
        }

        /// <summary>
        /// Crash a specific network adapter.
        /// </summary>
        /// <param name="adapterName">Name of the network adapter.</param>
        /// <returns>Error for the action.</returns>
        public static ActionError CrashAdapterByName(string adapterName)
        {
            return CrashAdapterByName(adapterName, false);
        }


        /// <summary>
        /// Crash a specific network adapter.
        /// </summary>
        /// <param name="adapterName">Name of the network adapter.</param>
        /// <returns>Error for the action.</returns>
        public static ActionError CrashAdapterByName(string adapterName, bool forceSave)
        {
            ActionError error = ActionError.NoError;

            Core.NetworkAdapter.NetworkAdapterCollection networkAdapterCollection = Core.NetworkAdapter.GetInstances("Name = '" + adapterName + "'");

            if (networkAdapterCollection.Count == 1)
            {
                foreach (Core.NetworkAdapter networkAdapter in networkAdapterCollection)
                {
                    CrashAdapterByMACAddress(networkAdapter.MACAddress, forceSave);
                }
            }
            else
            {
                error = ActionError.AdapterNotFound;
                GenerateConsoleLine("Network adapter not found.");
            }

            return error;
        }

        /// <summary>
        /// Crash a specific network adapter.
        /// </summary>
        /// <param name="networkAdapter">Network adapter object.</param>
        /// <returns>Error for the action.</returns>
        private static ActionError CrashAdapterByMACAddress(string macAddress, bool forceSave)
        {
            ActionError error = ActionError.NoError;

            Actions.GenerateConsoleLine(string.Empty, false);

            GenerateConsoleLine("Trying to crash the network adapter.");
            if (Environment.OSVersion.Version.Build >= 6000)
            {
                error = CrashAdapterBuild6000AndUp(macAddress);
            }
            else
            {
                error = CrashAdapterBuildBelow6000(macAddress, forceSave);
            }
            if (error == ActionError.NoError)
            {
                GenerateConsoleLine("Network adapter crashed successfully.");
            }
            return error;
        }

        /// <summary>
        /// Method used to crash a network adapter in a windows with a build number equal or higher to 6000.
        /// Ex. Vista, 2008 and 2008 R2
        /// </summary>
        /// <param name="macAddress">MAC address from the adapter that will be stoped.</param>
        /// <returns>The error from the action execution.</returns>
        private static ActionError CrashAdapterBuild6000AndUp(string macAddress)
        {
            ActionError error = ActionError.NoError;

            try
            {
                Core.NetworkAdapter.NetworkAdapterCollection networkAdapterCollection = Core.NetworkAdapter.GetInstances("MACAddress = '" + macAddress + "'");

                if (networkAdapterCollection.Count == 1)
                {
                    foreach (NetworkAdapter networkAdapter in networkAdapterCollection)
                    {
                        networkAdapter.Disable();
                    }
                }
                else
                {
                    error = ActionError.AdapterNotFound;
                    GenerateConsoleLine("Network adapter not found.");
                }
            }
            catch (Exception ex)
            {
                error = ActionError.UnableToCrash;
                GenerateConsoleLine("Problems crashing the network adapter: " + ex.Message);
            }
            return error;
        }

        /// <summary>
        /// Method used to crash a network adapter in a windows with a build number below to 6000.
        /// Ex. Xp, 2003, 2003 R2 and 2000
        /// </summary>
        /// <param name="macAddress">MAC address from the adapter that will be stoped.</param>
        /// <param name="forceSave">Indicate to force the configuration saving.</param>
        /// <returns>The error from the action execution.</returns>
        private static ActionError CrashAdapterBuildBelow6000(string macAddress, bool forceSave)
        {
            ActionError error = ActionError.NoError;
            try
            {
                Core.NetworkAdapterConfiguration.NetworkAdapterConfigurationCollection networkAdapterConfigurationCollection = Core.NetworkAdapterConfiguration.GetInstances("MACAddress = '" + macAddress + "'");

                if (networkAdapterConfigurationCollection.Count == 1)
                {
                    foreach (NetworkAdapterConfiguration networkAdapterConfiguration in networkAdapterConfigurationCollection)
                    {
                        if (forceSave)
                        {
                            error = SaveSettings(networkAdapterConfiguration);
                        }
                        if (error != ActionError.NoError)
                        {
                            string[] newIPAddress = { "169.254.0.10" };
                            string[] newIPSubnet = { "255.255.0.0" };
                            string[] newDefaultIPGateway = { "127.0.0.1" };
                            ushort[] newGatewayCostMetric = { 1 };
                            networkAdapterConfiguration.EnableStatic(newIPAddress, newIPSubnet);
                            networkAdapterConfiguration.SetGateways(newDefaultIPGateway, newGatewayCostMetric);
                        }
                    }
                }
                else
                {
                    error = ActionError.AdapterNotFound;
                    GenerateConsoleLine("Network adapter not found.");
                }
            }
            catch (Exception ex)
            {
                error = ActionError.UnableToCrash;
                GenerateConsoleLine("Problems crashing the network adapter: " + ex.Message);
            }
            return error;
        }

        /// <summary>
        /// Repair a specific network adapter.
        /// </summary>
        /// <param name="adapterName">Name of the network adapter.</param>
        /// <returns>Error for the action.</returns>
        public static ActionError RepairAdapterByName(string adapterName)
        {
            ActionError error = ActionError.NoError;

            Core.NetworkAdapter.NetworkAdapterCollection networkAdapterCollection = NetworkAdapter.GetInstances("Name = '" + adapterName + "'");

            if (networkAdapterCollection.Count == 1)
            {
                foreach (Core.NetworkAdapter networkAdapter in networkAdapterCollection)
                {
                    RepairAdapterByMACAddress(networkAdapter.MACAddress);
                }
            }
            else
            {
                error = ActionError.AdapterNotFound;
                GenerateConsoleLine("Network adapter not found.");
            }

            return error;
        }

        /// <summary>
        /// Repair a specific network adapter.
        /// </summary>
        /// <param name="networkAdapter">Network adapter object.</param>
        /// <returns>Error for the action.</returns>
        private static ActionError RepairAdapterByMACAddress(string macAddress)
        {
            ActionError error = ActionError.NoError;

            Actions.GenerateConsoleLine(string.Empty, false);

            GenerateConsoleLine("Trying to repair the network adapter.");
            if (Environment.OSVersion.Version.Build >= 6000)
            {
                error = RepairAdapterBuild6000AndUp(macAddress);
            }
            else
            {
                error = RepairAdapterBuildBelow6000(macAddress);
            }
            if (error == ActionError.NoError)
            {
                GenerateConsoleLine("Network adapter repaired successfully.");
            }
            return error;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="macAddress"></param>
        /// <returns></returns>
        private static ActionError RepairAdapterBuild6000AndUp(string macAddress)
        {
            ActionError error = ActionError.NoError;
            try
            {
                Core.NetworkAdapter.NetworkAdapterCollection networkAdapterCollection = Core.NetworkAdapter.GetInstances("MACAddress = '" + macAddress + "'");

                if (networkAdapterCollection.Count == 1)
                {
                    foreach (NetworkAdapter networkAdapter in networkAdapterCollection)
                    {
                        networkAdapter.Enable();
                    }
                }
                else
                {
                    error = ActionError.AdapterNotFound;
                    GenerateConsoleLine("Network adapter not found.");
                }
            }
            catch (Exception ex)
            {
                error = ActionError.UnableToRepair;
                GenerateConsoleLine("Problems repairing the network adapter: " + ex.Message);
            }
            return error;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="macAddress"></param>
        /// <returns></returns>
        private static ActionError RepairAdapterBuildBelow6000(string macAddress)
        {
            ActionError error = ActionError.NoError;
            try
            {
                Core.NetworkAdapterConfiguration.NetworkAdapterConfigurationCollection networkAdapterConfigurationCollection = Core.NetworkAdapterConfiguration.GetInstances("MACAddress = '" + macAddress + "'");

                if (networkAdapterConfigurationCollection.Count == 1)
                {
                    foreach (NetworkAdapterConfiguration networkAdapterConfiguration in networkAdapterConfigurationCollection)
                    {
                        if (networkAdapterConfiguration.DHCPEnabled)
                        {
                            networkAdapterConfiguration.RenewDHCPLease();
                        }
                        else
                        {
                            try
                            {
                                SaveInformation saveInformation;
                                error = LoadSettings(out saveInformation);
                                networkAdapterConfiguration.EnableStatic(saveInformation.IPAddress, saveInformation.IPSubnet);
                                networkAdapterConfiguration.SetGateways(saveInformation.DefaultIPGateway, saveInformation.GatewayCostMetric);

                            }
                            catch
                            {
                                error = ActionError.UnableToLoadConfigurationFile;
                                GenerateConsoleLine("Unable to load the network adapter configurations.");
                            }
                        }
                    }
                }
                else
                {
                    error = ActionError.AdapterNotFound;
                    GenerateConsoleLine("Network adapter not found.");
                }
            }
            catch (Exception ex)
            {
                error = ActionError.UnableToRepair;
                GenerateConsoleLine("Problems repairing the network adapter: " + ex.Message);
            }
            return error;
        }

        /// <summary>
        /// List available network adapter.
        /// </summary>
        /// <returns>Error for the action.</returns>
        /// <remarks>Only works on the console.</remarks>
        //TODO: Change "ListAdapters()" method so it can be reused in a future GUI.
        public static ActionError ListAdaptersConsole()
        {
            ActionError error = ActionError.NoError;

            try
            {
                NetworkAdapter.NetworkAdapterCollection networkAdapterCollection = ListAdapters();

                if (networkAdapterCollection.Count > 0)
                {
                    GenerateConsoleLine(String.Empty, false);
                    GenerateConsoleLine("Listing available network adapters:", false);
                    GenerateConsoleLine(String.Empty, false);
                    short counting = 1;
                    foreach (Core.NetworkAdapter networkAdapter in networkAdapterCollection)
                    {
                        GenerateConsoleLine("   " + counting.ToString() + ". " + networkAdapter.Name, false);
                        counting++;
                    }
                }
                else
                {
                    GenerateConsoleLine("No network adapter available.");
                }

            }
            catch (Exception ex)
            {
                error = ActionError.UnableToRepair;
                GenerateConsoleLine("Problems listing network adapters: " + ex.Message);
            }

            return error;
        }

        public static NetworkAdapter.NetworkAdapterCollection ListAdapters()
        {
            NetworkAdapter.NetworkAdapterCollection networkAdapterCollection;
            
            if (Environment.OSVersion.Version.Build >= 6000)
            {
                networkAdapterCollection = Core.NetworkAdapter.GetInstances("NetEnabled = 'False' OR NetEnabled = 'True'");
            }
            else
            {
                networkAdapterCollection = Core.NetworkAdapter.GetInstances();
            }
            
            return networkAdapterCollection;
        }

        private static ActionError SaveSettings(NetworkAdapterConfiguration networkAdapter)
        {
            try
            {
                SaveInformation saveInformation = new SaveInformation();
                saveInformation.IPAddress = networkAdapter.IPAddress;
                saveInformation.IPSubnet = networkAdapter.IPSubnet;
                saveInformation.DefaultIPGateway = networkAdapter.DefaultIPGateway;
                saveInformation.GatewayCostMetric = networkAdapter.GatewayCostMetric;

                string actualPath = Environment.CurrentDirectory;
                string savePath = actualPath + "\\" + saveFileName;

                if (File.Exists(savePath))
                {
                    File.Delete(savePath);
                }

                Stream stream = File.Open(savePath, FileMode.Create);
                BinaryFormatter binaryFormatter = new BinaryFormatter();

                binaryFormatter.Serialize(stream, saveInformation);

                stream.Close();

                return ActionError.NoError;
            }
            catch (Exception ex)
            {

                return ActionError.UnableToCreateConfigurationFile;
            }
        }

        private static ActionError LoadSettings(out SaveInformation saveInformation)
        {
            saveInformation = null;

            try
            {
                string actualPath = Environment.CurrentDirectory;
                string savePath = actualPath + "\\" + saveFileName;

                Stream stream = File.Open(savePath, FileMode.Open);
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                saveInformation = (SaveInformation)binaryFormatter.Deserialize(stream);
                stream.Close();

                return ActionError.NoError;
            }
            catch
            {
                return ActionError.ConfigurationFileNotFound;
            }
        }

        public static void TimedCrash(string adapterName, int totalTime, int crashes, int timeToRecover)
        {
            ActionError error = ActionError.NoError;

            Core.NetworkAdapterConfiguration.NetworkAdapterConfigurationCollection networkAdapters = Core.NetworkAdapterConfiguration.GetInstances("Description = '" + adapterName + "'");

            if (networkAdapters.Count == 1)
            {
                foreach (Core.NetworkAdapterConfiguration networkAdapter in networkAdapters)
                {
                    if (!networkAdapter.DHCPEnabled)
                    {
                        SaveSettings(networkAdapter);
                    }
                }
            }
            else
            {
                error = ActionError.AdapterNotFound;
                GenerateConsoleLine("Network adapter not found.");
            }

            if (error == ActionError.NoError)
            {
                Core.TimedCrash timedCrash = new Core.TimedCrash();
                timedCrash.Execute(adapterName, totalTime, crashes, timeToRecover);
            }
        }
    }
}
